﻿//Copyright (C) 2011  Matthew White

//This program is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either version 2
//of the License, or (at your option) any later version.

//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.

//You should have received a copy of the GNU General Public License
//along with this program; if not, write to the Free Software
//Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

namespace SccmBoundaryHealth
{
    using System;
    using System.Collections;
    using System.DirectoryServices;
    using System.DirectoryServices.ActiveDirectory;
    using System.Linq;
    using System.Net;
    using System.Security.Authentication;
    using System.Text;
    using System.Text.RegularExpressions;
    using Properties;
    using EnumExtensions;
    
    /// <summary>
    /// The program itself
    /// </summary>
    internal class Program
    {
        /// <summary>
        /// AD attribute name for ranged IP boundaries (upper)
        /// </summary>
        private static readonly string ATTR_MS_SMS_RANGED_IP_HIGH = Settings.Default.Attrib_SMSIPRangeHigh;
        /// <summary>
        /// AD attribute name for ranged IP boundaries (lower)
        /// </summary>
        private static readonly string ATTR_MS_SMS_RANGED_IP_LOW = Settings.Default.Attrib_SMSIPRangeLow;
        /// <summary>
        /// AD attribute name for roaming boundaries (contains IP subnets & AD site names)
        /// </summary>
        private static readonly string ATTR_MS_SMS_ROAMING_BOUNDS = Settings.Default.Attrib_RoamingBounds;
        /// <summary>
        /// AD attribute name for site code
        /// </summary>
        private static readonly string ATTR_MS_SMS_SITE_CODE = Settings.Default.Attrib_SMSSiteCode;
        /// <summary>
        /// AD class name for IP range boundary
        /// </summary>
        private static readonly string CLASS_MS_SMS_IPRANGE = Settings.Default.Class_SMSIPRangeBoundary;
        /// <summary>
        /// AD class name for SMS/SCCM site
        /// </summary>
        private static readonly string CLASS_MS_SMS_SITE = Settings.Default.Class_SMSSite;
        /// <summary>
        /// Verbosity of the program - 1 = not verbose, 4 = very verbose
        /// </summary>
        private static readonly byte DEBUGLEVEL = (byte)Settings.Default.DebugLevel;
        /// <summary>
        /// Data set used by the program to store and query for the site and boundary data
        /// </summary>
        private static SccmBoundaryHealth.ds ds = new SccmBoundaryHealth.ds();
        /// <summary>
        /// Regular expression for an IPv4 address
        /// </summary>
        private const string IPV4_REGEX = @"(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)";
        /// <summary>
        /// Regular expression for an IPv4 subnet mask
        /// </summary>
        private static readonly Regex REGEX_IPV4SUBNET = new Regex(@"^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$");
        /// <summary>
        /// Regular expression for an IPv6 prefix (as used by SCCM)
        /// </summary>
        private static readonly Regex REGEX_IPV6PREFIX = new Regex("^([0-9A-F]{4}):([0-9A-F]{4}):([0-9A-F]{4}):([0-9A-F]{4})$");
        /// <summary>
        /// Term used in the data set to denote an AD site boundary
        /// </summary>
        private static readonly string SCCM_BOUND_ADSITE = boundaryType.adsite.ToString("G");
        /// <summary>
        /// Term used in the data set to denote an IPv4 subnet boundary
        /// </summary>
        private static readonly string SCCM_BOUND_IP4SUBNET = boundaryType.ip4subnet.ToString("G");
        /// <summary>
        /// Term used in the data set to denote an IPv6boundary
        /// </summary>
        private static readonly string SCCM_BOUND_IP6PREFIX = boundaryType.ip6prefix.ToString("G");
        /// <summary>
        /// Location (DN) of the system management container in an AD domain
        /// </summary>
        private static readonly string SYSTEM_MGMT_CONTAINER = Settings.Default.SystemManagementContainer;

        public enum boundaryType
        {
            [EnumExtensions.StringValue("IPv4 Range")]
            ip4range=0,
            [EnumExtensions.StringValue("IPv4 Subnet")]
            ip4subnet,
            [EnumExtensions.StringValue("IPv6 Prefix")]
            ip6prefix,
            [EnumExtensions.StringValue("Active Directory Site")]
            adsite
        }
        
        protected static ArrayList healthViolations = new ArrayList();

        /// <summary>
        /// This is the function that kicks off the program
        /// </summary>
        /// <param name="args">The command line arguments</param>
        private static void Main(string[] args)
        {
            ds.WriteXmlSchema("schema.xsd");
            // there must a better way than this, but I can't seem to
            // instantiate a forest object to begin with
            try
            {
                Forest f = ParseArgsReturnForest(args);
                getPublishedSccmSiteBoundaries(f);
                getAdSiteInfo(f);
            }
            catch (ActiveDirectoryOperationException adopex)
            {
                OutConsole(adopex.Message, 1);
                Environment.Exit(1);
            }
            catch (AuthenticationException adauthex)
            {
                OutConsole(adauthex.Message, 1);
                Environment.Exit(1);
            }

            PopulateSccmSiteList();
            PerformBoundaryOverlapCheck();
            ReportAdSiteBoundaryHealth();
            
        }


        private static Forest ParseArgsReturnForest(string[] args)
        {
            if (args.Length > 0)
            {
                StringBuilder builder = new StringBuilder();
                builder.Append("\n");
                builder.AppendLine("Usage:");
                builder.AppendLine("SccmBoundaryHealth.exe\n\tRuns app using local forest and security context");
                builder.AppendLine("SccmBoundaryHealth.exe -forest=[ForestDNS] -user=[Domain\\UserName]\n\tRuns app using specified forest and security context");
                builder.Append("\n");
                Hashtable hashtable = ArgumentParser.Parse(args);
                if ((hashtable.ContainsKey("?") || hashtable.ContainsKey("h")) || (hashtable.ContainsKey("Invalid") || (hashtable.Count != 2)))
                {
                    Console.Write(builder);
                    Environment.Exit(-1);
                }
                else
                {
                    string name = null;
                    string username = null;
                    string password = null;
                    foreach (DictionaryEntry entry in hashtable)
                    {
                        string str4 = entry.Key.ToString().ToLower();
                        if (str4 != null)
                        {
                            if (!(str4 == "forest"))
                            {
                                if (str4 == "user")
                                {
                                    goto Label_00F5;
                                }
                            }
                            else
                            {
                                name = entry.Value.ToString();
                            }
                        }
                        continue;
                    Label_00F5:
                        username = entry.Value.ToString();
                    }
                    if ((name == null) || (username == null))
                    {
                        Console.Write(builder);
                        Environment.Exit(-1);
                    }
                    Console.WriteLine("Enter account password for {0}:", username.ToUpper());
                    password = Returnpassword();
                    DirectoryContext context = new DirectoryContext(DirectoryContextType.Forest, name, username, password);

                    return Forest.GetForest(context);

                }
            }

            return Forest.GetCurrentForest();

        }


        /// <summary>
        /// Connects to the AD forest and enumerates all sites and associated subnets and adds data to the data set
        /// </summary>
        /// <param name="f">The AD forest</param>
        private static void getAdSiteInfo(Forest f)
        {
            OutConsole("Getting AD Site Data...\n", 1);
            // set this table to be case insensitive
            ds.ADSiteData.CaseSensitive = false;

            foreach (ActiveDirectorySite site in f.Sites)
            {
                foreach (ActiveDirectorySubnet subnet in site.Subnets)
                {
                    string[] strArray = subnet.Name.Split(new char[] { '/' });
                    byte aDSiteIPVersion = 6;
                    if (REGEX_IPV4SUBNET.IsMatch(strArray[0]))
                    {
                        aDSiteIPVersion = 4;
                    }

                    ds.ADSiteData.AddADSiteDataRow(site.Name, strArray[0], aDSiteIPVersion, Convert.ToInt32(strArray[1]));

                    OutConsole(" -> {0},{1}/{2},{3}\n", 3, new object[] { site.Name, strArray[0], strArray[1], aDSiteIPVersion });
                }

                if (site.Subnets.Count == 0)
                {
                    OutConsole(" -> Site: {0} has no subnets assigned\n", 3, new object[] { site.Name }, ConsoleColor.Yellow);
                    // add dummy data row, this is poor but until the dataset designer is recreated
                    // this will haev to do
                    ds.ADSiteData.AddADSiteDataRow(site.Name, null, 0, 0);
                }
            }

            OutConsole("Getting AD Site Data... Done!\n\n", 1);

        }


        /// <summary>
        /// Connects to ad AD forest, works through all domains and collects SCCM boundary data and adds to the data set
        /// </summary>
        /// <param name="f">The AD forest</param>
        private static void getPublishedSccmSiteBoundaries(Forest f)
        {
            OutConsole("Getting SCCM Boundary Data...\n", 1);

            foreach (Domain domain in f.Domains)
            {
                try
                {
                    OutConsole(" Domain: {0}\n", 2, new object[] { domain.Name });

                    // Get the domain path, this will like: LDAP://domain.fqdn.local/DC=domain,DC=fqdn,DC=local
                    string path = domain.GetDirectoryEntry().Path;

                    // Split the path string on the "/" char, removing empty entries
                    char[] separator = "/".ToCharArray(0, 1);
                    string[] strArray = path.Split(separator, StringSplitOptions.RemoveEmptyEntries);

                    // extract the DN and Fqdn from the last and last but one array entries
                    string domainDn = strArray[strArray.GetUpperBound(0)];
                    string domainFqdn = strArray[strArray.GetUpperBound(0) - 1];

                    // Construct a new LDAP path including the systems management container
                    // e.g. "LDAP://domain.fqdn.local/CN=System Management,CN=System,DC=domain,DC=fqdn,DC=local"
                    StringBuilder builder = new StringBuilder();
                    builder.Append("LDAP://");
                    builder.Append(domainFqdn);
                    builder.Append("/");
                    builder.Append(SYSTEM_MGMT_CONTAINER);
                    builder.Append(",");
                    builder.Append(domainDn);

                    try
                    {
                        DirectoryEntry sysMgmtContainer = new DirectoryEntry(builder.ToString());

                        // loop through all the objects that we care about (sms sites & ranged IP bounds) and
                        // extract the useful info
                        foreach (DirectoryEntry sysMgmtContainerChildObject in sysMgmtContainer.Children)
                        {
                            if (sysMgmtContainerChildObject.SchemaClassName == CLASS_MS_SMS_SITE)
                            {
                                OutConsole("  Found mSSMSSite object: {0}\n", 4, new object[] { sysMgmtContainerChildObject.Name });
                                PropertyValueCollection siteRoamingBoundValues = sysMgmtContainerChildObject.Properties[ATTR_MS_SMS_ROAMING_BOUNDS];
                                PropertyValueCollection siteCodeValues = sysMgmtContainerChildObject.Properties[ATTR_MS_SMS_SITE_CODE];
                                if (siteRoamingBoundValues.Count == 0)
                                {
                                    OutConsole("  -> No mSSMSRoamingBoundaries found\n", 4);
                                }
                                if (siteCodeValues.Count == 0)
                                {
                                    OutConsole("  -> Could not read mSSMSSiteCode attribute for {0}, skipping\n", 2, new object[] {sysMgmtContainerChildObject.Name}, ConsoleColor.Yellow );
                                    break;
                                }
                                for (int i = 0; i < siteRoamingBoundValues.Count; i++)
                                {
                                    string boundary = siteRoamingBoundValues[i].ToString();
                                    string siteCode = string.Empty;
                                    siteCode = siteCodeValues[0].ToString().ToUpper();
                                    string boundaryType = getSccmBoundaryType(boundary);

                                    if (boundaryType == SCCM_BOUND_IP4SUBNET)
                                    {
                                        boundary = ConvertIpOctet2Int32(boundary).ToString();
                                    }
                                    if (boundaryType == SCCM_BOUND_ADSITE)
                                    {
                                        boundary = boundary.ToUpper();
                                    }
                                    if (!(boundary == "NONE"))
                                    {
                                        ds.SccmBoundaryData.AddSccmBoundaryDataRow(siteCode, boundary, boundaryType);
                                    }
                                }
                            }
                            if (sysMgmtContainerChildObject.SchemaClassName == CLASS_MS_SMS_IPRANGE)
                            {
                                PropertyValueCollection msSmsSiteCodeAttrib = sysMgmtContainerChildObject.Properties[ATTR_MS_SMS_SITE_CODE];
                                PropertyValueCollection msSmsRangedIpHighAttrib = sysMgmtContainerChildObject.Properties[ATTR_MS_SMS_RANGED_IP_HIGH];
                                PropertyValueCollection msSmsRangedIpLowAttrib = sysMgmtContainerChildObject.Properties[ATTR_MS_SMS_RANGED_IP_LOW];
                                string str7 = msSmsSiteCodeAttrib[0].ToString().ToUpper();
                                string text1 = msSmsRangedIpLowAttrib[0] + "|" + msSmsRangedIpHighAttrib[0];
                                int iPRangeHigh = Convert.ToInt32(msSmsRangedIpHighAttrib[0]);
                                int iPRangeLow = Convert.ToInt32(msSmsRangedIpLowAttrib[0]);
                                ds.SccmBoundaryIPRangeData.AddSccmBoundaryIPRangeDataRow(str7, iPRangeLow, iPRangeHigh);
                            }
                            sysMgmtContainer.Dispose();
                        } // end foreach loop
                    } // end try

                    catch (DirectoryServicesCOMException)
                    {
                        OutConsole(" -> No system management container found\n", 2, ConsoleColor.Yellow);
                    }

                }
                catch (ActiveDirectoryObjectNotFoundException)
                {
                    OutConsole(" -> No system management container found\n", 2, ConsoleColor.Yellow);
                }

                catch (ActiveDirectoryServerDownException)
                {
                    OutConsole(" -> Server not available\n", 2, new object[] { domain.Name }, ConsoleColor.Yellow);
                }
                catch (UnauthorizedAccessException)
                {
                    OutConsole(" -> Access denied for the domain: {0}\n    Continuing...\n", 1, new object[] { domain.Name }, ConsoleColor.Yellow);
                }
                // this exception is thrown if the security context is denied access to the container
                catch (System.Runtime.InteropServices.COMException)
                {
                    OutConsole(" -> Access denied for the domain: {0}\n    Continuing...\n", 1, new object[] { domain.Name }, ConsoleColor.Yellow);
                }
            }


            foreach (SccmBoundaryHealth.ds.SccmBoundaryDataRow row in ds.SccmBoundaryData.Rows)
            {
                if (row.BoundaryType == SCCM_BOUND_IP4SUBNET)
                {
                    OutConsole("  -> {0},{1},{2}\n", 3, new object[] { row.SiteCode, ConvertIpInt322Octet(Convert.ToInt32(row.BoundaryData)), row.BoundaryType });
                }
                else
                {
                    OutConsole("  -> {0},{1},{2}\n", 3, new object[] { row.SiteCode, row.BoundaryData, row.BoundaryType });
                }
            }


            foreach (SccmBoundaryHealth.ds.SccmBoundaryIPRangeDataRow row2 in ds.SccmBoundaryIPRangeData.Rows)
            {
                OutConsole("  -> {0},{1}-{2}\n", 3, new object[] { row2.SiteCode, ConvertIpInt322Octet(row2.IPRangeLow), ConvertIpInt322Octet(row2.IPRangeHigh) });
            }


            OutConsole("Getting SCCM Boundary Data... Done!\n\n", 1);
        }


        /// <summary>
        /// Converts an IP address from Int format to octets
        /// </summary>
        /// <param name="ipInt">IP address as integar</param>
        /// <returns></returns>
        private static string ConvertIpInt322Octet(int ipInt)
        {
            return new IPAddress(BitConverter.GetBytes(ipInt).Reverse<byte>().ToArray<byte>()).ToString();
        }


        /// <summary>
        /// Converts an IP address from octets to integar format
        /// </summary>
        /// <param name="ipOctet">IPaddress object</param>
        /// <returns></returns>
        private static int ConvertIpOctet2Int32(string ipOctet)
        {
            Regex regex = new Regex(@"^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$");
            if (!regex.IsMatch(ipOctet))
            {
                throw new InvalidIpAddressException(ipOctet);
            }
            return BitConverter.ToInt32(IPAddress.Parse(ipOctet).GetAddressBytes().Reverse<byte>().ToArray<byte>(), 0);
        }








        private static string getSccmBoundaryType(string boundary)
        {
            if (REGEX_IPV4SUBNET.IsMatch(boundary))
            {
                return SCCM_BOUND_IP4SUBNET;
            }
            if (REGEX_IPV6PREFIX.IsMatch(boundary))
            {
                return SCCM_BOUND_IP6PREFIX;
            }
            return SCCM_BOUND_ADSITE;
        }


        private static int GetSubnetHighIpAsInt(int ipint, int sm)
        {
            int num = ~(ipint | sm);
            return (num + ipint);
        }


        private static int GetSubnetMaskAsInt(int bits)
        {
            int num = 0;
            for (int i = 1; i <= 0x20; i++)
            {
                if (i <= bits)
                {
                    num++;
                }
                if (i != 0x20)
                {
                    num = num << 1;
                }
            }
            return num;
        }


        private static int GuessClassfulSubnetMask(int ipint)
        {
            if ((ipint & -16777216) == ipint)
            {
                return -16777216;
            }
            if ((ipint & -65536) == ipint)
            {
                return -65536;
            }
            return -256;
        }

        
        /// <summary>
        /// Calculates if two ip ranges (as ints) overlap
        /// </summary>
        /// <param name="ip1low">IP range 1 low address</param>
        /// <param name="ip1high">IP range 1 high address</param>
        /// <param name="ip2low">IP range 2 low address</param>
        /// <param name="ip2high">IP range 2 high address</param>
        /// <returns>bool</returns>
        private static bool IsOverlap(int ip1low, int ip1high, int ip2low, int ip2high)
        {
            return ((((ip1low >= ip2low) & (ip1low <= ip2high)) | ((ip1high >= ip2low) & (ip1high <= ip2high)))
                | (((ip2low >= ip1low) & (ip2low <= ip1high)) | ((ip2high >= ip1low) & (ip2high <= ip1high))));
        }


        /// <summary>
        /// Outputs text to the console
        /// </summary>
        /// <param name="message">message to output</param>
        /// <param name="messageLevel">verbosity, will display if the debuglevel of the program is greater than or equal to this</param>
        /// <param name="foregroundCol">FG ConsoleColor</param>
        /// <param name="backgroundCol">BG COnsoleColor</param>
        private static void OutConsole(string message, int messageLevel, ConsoleColor foregroundCol, ConsoleColor backgroundCol)
        {
            if (messageLevel <= DEBUGLEVEL)
            {

                Console.ForegroundColor = foregroundCol;
                Console.BackgroundColor = backgroundCol;

                Console.Write(message);

                Console.ResetColor();
            }
        }


        /// <summary>
        /// Outputs text to the console
        /// </summary>
        /// <param name="message">message to output</param>
        /// <param name="messageLevel">verbosity, will display if the debuglevel of the program is greater than or equal to this</param>
        /// <param name="foregroundCol">FG ConsoleColor</param>
        private static void OutConsole(string message, int messageLevel, ConsoleColor foregroundCol)
        {
            if (messageLevel <= DEBUGLEVEL)
            {
                Console.ForegroundColor = foregroundCol;

                Console.Write(message);

                Console.ResetColor();
            }
        }


        /// <summary>
        /// Outputs text to the console
        /// </summary>
        /// <param name="message">message to output</param>
        /// <param name="messageLevel">verbosity, will display if the debuglevel of the program is greater than or equal to this</param>
        private static void OutConsole(string message, int messageLevel)
        {
            if (messageLevel <= DEBUGLEVEL)
            {
                Console.Write(message);
            }
        }


        /// <summary>
        /// Outputs text to the console
        /// </summary>
        /// <param name="message">message to output</param>
        /// <param name="messageLevel">verbosity, will display if the debuglevel of the program is greater than or equal to this</param>
        /// <param name="stringElements">Object array of parameters to substitute into the string using {0}, {1} etc</param>
        /// <param name="foregroundCol">FG ConsoleColor</param>
        /// <param name="backgroundCol">BG ConsoleColor</param>
        private static void OutConsole(string message, int messageLevel, object[] stringElements, ConsoleColor foregroundCol, ConsoleColor backgroundCol)
        {
            if (messageLevel <= DEBUGLEVEL)
            {
                Console.ForegroundColor = foregroundCol;
                Console.BackgroundColor = backgroundCol;

                Console.Write(message, stringElements);

                Console.ResetColor();
            }
        }


        /// <summary>
        /// Outputs text to the console
        /// </summary>
        /// <param name="message">message to output</param>
        /// <param name="messageLevel"></param>
        /// <param name="stringElements"></param>
        /// <param name="foregroundCol"></param>
        private static void OutConsole(string message, int messageLevel, object[] stringElements, ConsoleColor foregroundCol)
        {
            if (messageLevel <= DEBUGLEVEL)
            {
                Console.ForegroundColor = foregroundCol;

                Console.Write(message, stringElements);

                Console.ResetColor();
            }
        }


        /// <summary>
        /// Outputs text to the console
        /// </summary>
        /// <param name="message">message to output</param>
        /// <param name="messageLevel">verbosity, will display if the debuglevel of the program is greater than or equal to this</param>
        /// <param name="stringElements">Object array of parameters to substitute into the string using {0}, {1} etc</param>
        private static void OutConsole(string message, int messageLevel, object[] stringElements)
        {
            if (messageLevel <= DEBUGLEVEL)
            {
                Console.Write(message, stringElements);
            }
        }


        /// <summary>
        /// This is where the real work is done:
        /// 1st step - query data
        /// 2nd step - loop through all SCCM sites and check conflicts for:
        /// ad vs ad
        /// ad vs ip subnet
        /// ad vs iprange
        /// iprange vs iprange
        /// iprange vs ip subnet
        /// ipsubnet vs ipsubnet
        /// TODO: ipv6 prefix vs ipv6 prefix
        /// TODO: ipv6 prefix vs ad site
        /// </summary>
        private static void PerformBoundaryOverlapCheck()
        {
            OutConsole("Boundary Overlap Check...\n", 1);

            foreach (SccmBoundaryHealth.ds.SccmSitesRow sccmSiteRow in ds.SccmSites.Rows)
            {
                OutConsole(" Site: {0}\n", 2,new object[] { sccmSiteRow.SiteCode });

                string thisSccmSite = sccmSiteRow.SiteCode.ToUpper();

                // Query the data source to give us arrays of the data we will need to compute the overlap checks

                // First the get the other, unprocessed SCCM sites, these will be the ones that we need to perform comparisons on.
                SccmBoundaryHealth.ds.SccmSitesRow[] otherSccmSites = (SccmBoundaryHealth.ds.SccmSitesRow[]) ds.SccmSites.Select("isProcessed = 'False' AND SiteCode <> '" + thisSccmSite + "'");
                if (otherSccmSites.GetLength(0) == 0)
                {
                    break;
                }

                // Get this SCCM site's AD boundaries
                StringBuilder builder = new StringBuilder();
                builder.Append("SiteCode = '");
                builder.Append(thisSccmSite);
                builder.Append("' AND BoundaryType = '");
                builder.Append(SCCM_BOUND_ADSITE);
                builder.Append("'");
                SccmBoundaryHealth.ds.SccmBoundaryDataRow[] thisSccmSiteAdBounds = (SccmBoundaryHealth.ds.SccmBoundaryDataRow[]) ds.SccmBoundaryData.Select(builder.ToString());
                builder.Clear();

                // Get this SCCM site's IP4 subnet boundaries
                builder = new StringBuilder();
                builder.Append("SiteCode = '");
                builder.Append(thisSccmSite);
                builder.Append("' AND BoundaryType = '");
                builder.Append(SCCM_BOUND_IP4SUBNET);
                builder.Append("'");
                SccmBoundaryHealth.ds.SccmBoundaryDataRow[] thisSccmSiteIp4SubnetBounds = (SccmBoundaryHealth.ds.SccmBoundaryDataRow[]) ds.SccmBoundaryData.Select(builder.ToString());
                builder.Clear();

                // Get this SCCM site's IP range boundaires
                builder = new StringBuilder();
                builder.Append("SiteCode = '");
                builder.Append(thisSccmSite);
                builder.Append("'");
                SccmBoundaryHealth.ds.SccmBoundaryIPRangeDataRow[] thisSccmSiteIpRangeBounds = (SccmBoundaryHealth.ds.SccmBoundaryIPRangeDataRow[]) ds.SccmBoundaryIPRangeData.Select(builder.ToString());
                builder.Clear();

                // Get this SCCM site's IP6 boundaires
                builder = new StringBuilder();
                builder.Append("SiteCode = '");
                builder.Append(thisSccmSite);
                builder.Append("' AND BoundaryType = '");
                builder.Append(SCCM_BOUND_IP6PREFIX);
                builder.Append("'");
                SccmBoundaryHealth.ds.SccmBoundaryDataRow[] thisSccmSiteIp6Bounds = (SccmBoundaryHealth.ds.SccmBoundaryDataRow[]) ds.SccmBoundaryData.Select(builder.ToString());
                builder.Clear();

                
                // make two arraylists of AD site data associated with this site, one for IPv4 and one for IPv6
                
                ArrayList thisSccmSiteIp4AdSiteNamesTemp = new ArrayList();
                ArrayList thisSccmSiteIp6AdSiteNamesTemp = new ArrayList();

                foreach (SccmBoundaryHealth.ds.ADSiteDataRow adSiteDataRow in ds.ADSiteData.Rows)
                {
                    foreach (SccmBoundaryHealth.ds.SccmBoundaryDataRow thisSccmSiteAdBound in thisSccmSiteAdBounds)
                    {
                        if (thisSccmSiteAdBound.BoundaryData.ToUpper() == adSiteDataRow.ADSiteName.ToUpper())
                        {
                            switch (adSiteDataRow.ADSiteIPVersion)
                            {
                                case 4:
                                    thisSccmSiteIp4AdSiteNamesTemp.Add(adSiteDataRow);
                                    break;

                                case 6:
                                    thisSccmSiteIp6AdSiteNamesTemp.Add(adSiteDataRow);
                                    break;
                            }
                        }
                    }
                }

                // Convert the above arraylists to arrays
                SccmBoundaryHealth.ds.ADSiteDataRow[] thisSccmSiteIp4AdSites = (SccmBoundaryHealth.ds.ADSiteDataRow[]) thisSccmSiteIp4AdSiteNamesTemp.ToArray(typeof(SccmBoundaryHealth.ds.ADSiteDataRow));
                SccmBoundaryHealth.ds.ADSiteDataRow[] thisSccmSiteIp6AdSiteNames = (SccmBoundaryHealth.ds.ADSiteDataRow[]) thisSccmSiteIp6AdSiteNamesTemp.ToArray(typeof(SccmBoundaryHealth.ds.ADSiteDataRow));

                // Now start to build queries to get the data for the unprocessed sites other that than this one

                // Build a query statement to include all other SCCM site codes
                builder = new StringBuilder();
                builder.Append("(");
                for (int i = 0; i <= otherSccmSites.GetUpperBound(0); i++)
                {
                    builder.Append("SiteCode = '");
                    builder.Append(otherSccmSites[i].SiteCode);
                    if (i < otherSccmSites.GetUpperBound(0))
                    {
                        builder.Append("' OR ");
                    }
                    else
                    {
                        builder.Append("') ");
                    }
                }
                
                string otherSccmSiteCodesQuery = builder.ToString();
                builder.Clear();

                // Get other SCCM sites' AD site boundaries
                builder = new StringBuilder();
                builder.Append(otherSccmSiteCodesQuery);
                builder.Append("AND BoundaryType = '");
                builder.Append(SCCM_BOUND_ADSITE);
                builder.Append("'");
                SccmBoundaryHealth.ds.SccmBoundaryDataRow[] otherSccmSitesAdBounds = (SccmBoundaryHealth.ds.SccmBoundaryDataRow[]) ds.SccmBoundaryData.Select(builder.ToString());
                builder.Clear();
                
                // ... now IPv4 subnet boundaries
                builder = new StringBuilder();
                builder.Append(otherSccmSiteCodesQuery);
                builder.Append("AND BoundaryType = '");
                builder.Append(SCCM_BOUND_IP4SUBNET);
                builder.Append("'");
                SccmBoundaryHealth.ds.SccmBoundaryDataRow[] otherSccmSitesIp4SubnetBounds = (SccmBoundaryHealth.ds.SccmBoundaryDataRow[]) ds.SccmBoundaryData.Select(builder.ToString());
                builder.Clear();
                
                // ... IP range boundaries
                builder = new StringBuilder();
                builder.Append(otherSccmSiteCodesQuery);
                SccmBoundaryHealth.ds.SccmBoundaryIPRangeDataRow[] otherSccmSitesIpRangeBounds = (SccmBoundaryHealth.ds.SccmBoundaryIPRangeDataRow[]) ds.SccmBoundaryIPRangeData.Select(builder.ToString());
                builder.Clear();
                
                // ... finally the IPv6 boundaries
                builder = new StringBuilder();
                builder.Append(otherSccmSiteCodesQuery);
                builder.Append("AND BoundaryType = '");
                builder.Append(SCCM_BOUND_IP6PREFIX);
                builder.Append("'");
                SccmBoundaryHealth.ds.SccmBoundaryDataRow[] otherSccmSitesIp6Bounds = (SccmBoundaryHealth.ds.SccmBoundaryDataRow[]) ds.SccmBoundaryData.Select(builder.ToString());
                builder.Clear();

                // Now get AD site data for the other SCCM sites
                ArrayList otherSccmSitesIp4AdSiteNamesTemp = new ArrayList();
                ArrayList otherSccmSitesIp6AdSiteNamesTemp = new ArrayList();
                
                foreach (SccmBoundaryHealth.ds.ADSiteDataRow adSiteDataRow in ds.ADSiteData.Rows)
                {
                    foreach (SccmBoundaryHealth.ds.SccmBoundaryDataRow otherSccmSitesAdBound in otherSccmSitesAdBounds)
                    {
                        if (otherSccmSitesAdBound.BoundaryData.ToString().ToUpper() != adSiteDataRow.ADSiteName.ToUpper())
                        {
                            switch (adSiteDataRow.ADSiteIPVersion)
                            {
                                case 4:
                                    otherSccmSitesIp4AdSiteNamesTemp.Add(adSiteDataRow);
                                    break;

                                case 6:
                                    otherSccmSitesIp6AdSiteNamesTemp.Add(adSiteDataRow);
                                    break;
                            }
                        }
                    }
                }

                // Again, convert the arraylists to arrays
                SccmBoundaryHealth.ds.ADSiteDataRow[] otherSccmSitesIp4AdSiteNames = (SccmBoundaryHealth.ds.ADSiteDataRow[]) otherSccmSitesIp4AdSiteNamesTemp.ToArray(typeof(SccmBoundaryHealth.ds.ADSiteDataRow));
                SccmBoundaryHealth.ds.ADSiteDataRow[] otherSccmSitesIp6AdSiteNames = (SccmBoundaryHealth.ds.ADSiteDataRow[]) otherSccmSitesIp6AdSiteNamesTemp.ToArray(typeof(SccmBoundaryHealth.ds.ADSiteDataRow));
                

                // Check this AD site vs other AD site conflicts...
                foreach (SccmBoundaryHealth.ds.SccmBoundaryDataRow thisSccmSiteAdBound in thisSccmSiteAdBounds)
                {
                    string thisSccmSiteAdBoundName = thisSccmSiteAdBound.BoundaryData.ToUpper();

                    OutConsole("  -> Checking: {0}\n", 4, new object[] { thisSccmSiteAdBoundName });

                    // check AD site -> AD site conflicts

                    foreach (SccmBoundaryHealth.ds.SccmBoundaryDataRow otherSccmSitesAdBound in otherSccmSitesAdBounds)
                    {
                        OutConsole("    -> With: {0}\n", 4, new object[] { otherSccmSitesAdBound.BoundaryData });

                        if (thisSccmSiteAdBoundName == otherSccmSitesAdBound.BoundaryData.ToUpper())
                        {
                            builder = new StringBuilder();
                            builder.Append("       CONFLICT\n");
                            builder.Append("        Site1: ");
                            builder.Append(thisSccmSiteAdBound.SiteCode);
                            builder.Append(", Boundary ");
                            builder.Append(thisSccmSiteAdBoundName);
                            builder.Append("\n        Site2: ");
                            builder.Append(otherSccmSitesAdBound.SiteCode);
                            builder.Append(", Boundary ");
                            builder.Append(otherSccmSitesAdBound.BoundaryData);
                            builder.Append("\n");

                            OutConsole(builder.ToString(), 0, ConsoleColor.Red, ConsoleColor.Black);
                            builder.Clear();
                        }
                    }

                    // check this AD site vs other ip4 subnets

                    // get this AD site's associated subnets
                    foreach (SccmBoundaryHealth.ds.ADSiteDataRow thisSccmSiteIp4AdSite in thisSccmSiteIp4AdSites)
                    {
                        OutConsole("  -> Checking: {0}\n", 4, new object[] { thisSccmSiteIp4AdSite.ADSiteName } );

                        // Convert the IP address to ints to make the overlap calcs easier
                        ConvertIpOctet2Int32(thisSccmSiteIp4AdSite.ADSiteIPData);

                        int subnetMaskAsInt = GetSubnetMaskAsInt(thisSccmSiteIp4AdSite.ADSiteSubnetBits);
                        int thisSccmAdSiteSubnetInt = ConvertIpOctet2Int32(thisSccmSiteIp4AdSite.ADSiteIPData);
                        int thisSccmAdSiteSubnetHighIpInt = GetSubnetHighIpAsInt(thisSccmAdSiteSubnetInt, subnetMaskAsInt);
                        
                        // now we have the upper and lower, compare to other sites ip4 subnet bounds
                        // I have to guess the subnet mask of the IP4 subnet bounds as SCCM does not store it

                        foreach (SccmBoundaryHealth.ds.SccmBoundaryDataRow otherSccmSitesIp4SubnetBound in otherSccmSitesIp4SubnetBounds)
                        {
                            int sm = GuessClassfulSubnetMask(Convert.ToInt32(otherSccmSitesIp4SubnetBound.BoundaryData));
                            int otherSccmSiteIp4SubnetAsInt = Convert.ToInt32(otherSccmSitesIp4SubnetBound.BoundaryData);
                            int otherSccmSiteIp4SubnetHighIpAsInt = GetSubnetHighIpAsInt(otherSccmSiteIp4SubnetAsInt, sm);

                            OutConsole("    -> IPv4 Subnet: {0}", 4, new object[] { ConvertIpInt322Octet(Convert.ToInt32(otherSccmSitesIp4SubnetBound.BoundaryData)) } );
                            OutConsole("    -> Warning, subnet mask of: {0} has been guessed due to SCCM not storing the mask length",
                                4,  new object[] { ConvertIpInt322Octet(sm) }, ConsoleColor.Yellow, ConsoleColor.Black );

                            // Calculate the overlap
                            if (IsOverlap(thisSccmAdSiteSubnetInt, thisSccmAdSiteSubnetHighIpInt, otherSccmSiteIp4SubnetAsInt, otherSccmSiteIp4SubnetHighIpAsInt))
                            //if (((otherSccmSiteIp4SubnetAsInt >= thisSccmAdSiteSubnetInt) & (otherSccmSiteIp4SubnetAsInt <= thisSccmAdSiteSubnetHighIpInt)) 
                            //    | ((otherSccmSiteIp4SubnetAsInt <= thisSccmAdSiteSubnetInt) & (otherSccmSiteIp4SubnetHighIpAsInt >= thisSccmAdSiteSubnetHighIpInt)))
                            {
                                builder = new StringBuilder();
                                builder.Append("       POSSIBLE CONFLICT\n         Site1: ");
                                builder.Append(thisSccmSiteAdBound.SiteCode);
                                builder.Append(", Boundary ");
                                builder.Append(thisSccmSiteAdBoundName);
                                builder.Append(" (subnet ");
                                builder.Append(thisSccmSiteIp4AdSite.ADSiteIPData);
                                builder.Append("\n         Site2: ");
                                builder.Append(otherSccmSitesIp4SubnetBound.SiteCode);
                                builder.Append(", Boundary ");
                                builder.Append(ConvertIpInt322Octet(Convert.ToInt32(otherSccmSitesIp4SubnetBound.BoundaryData)));
                                builder.Append("/");
                                builder.Append(ConvertIpInt322Octet(sm));
                                builder.Append(" (subnet mask is guessed due to SCCM not storing it)\n");
                                OutConsole(builder.ToString(), 0, ConsoleColor.Red);
                                builder.Clear();
                            }
                        }

                        // now compare this AD site boundary with other ip4 range boundaries

                        foreach (SccmBoundaryHealth.ds.SccmBoundaryIPRangeDataRow otherSccmSitesIpRangeBound in otherSccmSitesIpRangeBounds)
                        {
                            OutConsole("    -> IPv4 Range: {0}:{1}-{2}", 4, new object[] 
                            { otherSccmSitesIpRangeBound.SiteCode, ConvertIpInt322Octet(otherSccmSitesIpRangeBound.IPRangeLow), 
                                ConvertIpInt322Octet(otherSccmSitesIpRangeBound.IPRangeHigh)  });

                            if (IsOverlap(otherSccmSitesIpRangeBound.IPRangeLow, otherSccmSitesIpRangeBound.IPRangeHigh, thisSccmAdSiteSubnetInt, thisSccmAdSiteSubnetHighIpInt))
                            {
                                builder = new StringBuilder();
                                builder.Append("       CONFLICT\n         Site1: ");
                                builder.Append(thisSccmSiteAdBound.SiteCode);
                                builder.Append(", Boundary ");
                                builder.Append(thisSccmSiteAdBoundName);
                                builder.Append(" (subnet ");
                                builder.Append(thisSccmSiteIp4AdSite.ADSiteIPData);
                                builder.Append(")\n         Site2: ");
                                builder.Append(otherSccmSitesIpRangeBound.SiteCode);
                                builder.Append(", Boundary ");
                                builder.Append(ConvertIpInt322Octet(otherSccmSitesIpRangeBound.IPRangeLow));
                                builder.Append("-");
                                builder.Append(ConvertIpInt322Octet(otherSccmSitesIpRangeBound.IPRangeHigh));
                                builder.Append("\n");
                                OutConsole(builder.ToString(), 0, ConsoleColor.Red);
                                builder.Clear();
                            }
                        }
                    }
                }

                // Now compare IP range boundaries with...

                foreach (SccmBoundaryHealth.ds.SccmBoundaryIPRangeDataRow thisSccmSiteIpRangeBound in thisSccmSiteIpRangeBounds)
                {
                    OutConsole("  -> Checking {0}:{1}-{2}", 4, new object[] 
                        { thisSccmSiteIpRangeBound.SiteCode, ConvertIpInt322Octet(thisSccmSiteIpRangeBound.IPRangeLow), ConvertIpInt322Octet(thisSccmSiteIpRangeBound.IPRangeHigh) });

                    // other IP range boundaries
                    foreach (SccmBoundaryHealth.ds.SccmBoundaryIPRangeDataRow otherSccmSitesIpRangeBound in otherSccmSitesIpRangeBounds)
                    {
                        OutConsole("    -> With {0}:{1}-{2}", 4, new object[] {
                            otherSccmSitesIpRangeBound.SiteCode, ConvertIpInt322Octet(otherSccmSitesIpRangeBound.IPRangeLow),
                            ConvertIpInt322Octet(otherSccmSitesIpRangeBound.IPRangeHigh) });

                        if (IsOverlap(thisSccmSiteIpRangeBound.IPRangeLow, thisSccmSiteIpRangeBound.IPRangeHigh, otherSccmSitesIpRangeBound.IPRangeLow, otherSccmSitesIpRangeBound.IPRangeHigh))
                        {
                            builder = new StringBuilder();
                            builder.Append("       CONFLICT\n         Site1: ");
                            builder.Append(thisSccmSiteIpRangeBound.SiteCode);
                            builder.Append(", Boundary ");
                            builder.Append(ConvertIpInt322Octet(thisSccmSiteIpRangeBound.IPRangeLow));
                            builder.Append("-");
                            builder.Append(ConvertIpInt322Octet(thisSccmSiteIpRangeBound.IPRangeHigh));
                            builder.Append("\n         Site2: ");
                            builder.Append(otherSccmSitesIpRangeBound.SiteCode);
                            builder.Append(", Boundary ");
                            builder.Append(ConvertIpInt322Octet(otherSccmSitesIpRangeBound.IPRangeLow));
                            builder.Append("-");
                            builder.Append(ConvertIpInt322Octet(otherSccmSitesIpRangeBound.IPRangeHigh));
                            builder.Append("\n");
                            OutConsole(builder.ToString(), 0, ConsoleColor.Red);
                            builder.Clear();
                        }
                    }

                    // next, compare this IP range with other IP4 subnets...

                    foreach (SccmBoundaryHealth.ds.SccmBoundaryDataRow otherSccmSitesIp4SubnetBound in otherSccmSitesIp4SubnetBounds)
                    {
                        int otherIp4SubnetBoundMask = GuessClassfulSubnetMask(Convert.ToInt32(otherSccmSitesIp4SubnetBound.BoundaryData));
                        int otherIp4SubnetLowIp = Convert.ToInt32(otherSccmSitesIp4SubnetBound.BoundaryData);
                        int otherIp4SubnetHighIp = GetSubnetHighIpAsInt(otherIp4SubnetLowIp, otherIp4SubnetBoundMask);

                        OutConsole("    -> With {0}:{1}/{2}\n", 4, new object[] {
                            otherSccmSitesIp4SubnetBound.SiteCode, 
                            ConvertIpInt322Octet(Convert.ToInt32(otherSccmSitesIp4SubnetBound.BoundaryData)), 
                            ConvertIpInt322Octet(otherIp4SubnetBoundMask) } );
                        OutConsole("       Subnet mask has been guessed due to SCCM not storing it", 4, ConsoleColor.Yellow);

                        if (IsOverlap(thisSccmSiteIpRangeBound.IPRangeLow, thisSccmSiteIpRangeBound.IPRangeHigh, otherIp4SubnetLowIp, otherIp4SubnetHighIp))
                        {
                            builder = new StringBuilder();
                            builder.Append("       CONFLICT\n         Site1: ");
                            builder.Append(thisSccmSiteIpRangeBound.SiteCode);
                            builder.Append(", Boundary ");
                            builder.Append(ConvertIpInt322Octet(thisSccmSiteIpRangeBound.IPRangeLow));
                            builder.Append("-");
                            builder.Append(ConvertIpInt322Octet(thisSccmSiteIpRangeBound.IPRangeHigh));
                            builder.Append("\n         Site2: ");
                            builder.Append(otherSccmSitesIp4SubnetBound.SiteCode);
                            builder.Append(", Boundary ");
                            builder.Append(ConvertIpInt322Octet(Convert.ToInt32(otherSccmSitesIp4SubnetBound.BoundaryData)));
                            builder.Append("/");
                            builder.Append(ConvertIpInt322Octet(otherIp4SubnetBoundMask));
                            builder.Append(" (subnet mask guessed as SCCM does not store it)\n");
                            OutConsole(builder.ToString(), 4, ConsoleColor.Red);
                            builder.Clear();
                        }
                    }
                }

                // now move on to comparing this subnet boundaries with...

                foreach (SccmBoundaryHealth.ds.SccmBoundaryDataRow thisSccmSiteIp4SubnetBound in thisSccmSiteIp4SubnetBounds)
                {
                    // again, have to guess the subnet mask :(
                    int thisIpv4Subnet = Convert.ToInt32(thisSccmSiteIp4SubnetBound.BoundaryData);
                    int thisIpv4SubnetMaskGuess = GuessClassfulSubnetMask(thisIpv4Subnet);
                    int thisIpv4SubnetHighIp = GetSubnetHighIpAsInt(thisIpv4Subnet, thisIpv4SubnetMaskGuess);

                    OutConsole("  -> Checking {0}:{1}\n", 4, new object[] { thisSccmSiteIp4SubnetBound.SiteCode, ConvertIpInt322Octet(thisIpv4Subnet) });
                    OutConsole(" -> Subnet mask {0} guessed as SCCM does not store it", 4, new object[] { ConvertIpInt322Octet(thisIpv4SubnetMaskGuess) }, ConsoleColor.Yellow );

                    // compare this ipsubnet vs other ipsubnet
 
                    foreach (SccmBoundaryHealth.ds.SccmBoundaryDataRow otherSccmSitesIp4SubnetBound in otherSccmSitesIp4SubnetBounds)
                    {
                        int otherIpv4Subnet = Convert.ToInt32(otherSccmSitesIp4SubnetBound.BoundaryData);
                        int otherIpv4SubnetMaskGuess = GuessClassfulSubnetMask(otherIpv4Subnet);
                        int otherIpv4SubnetHighIp = GetSubnetHighIpAsInt(otherIpv4Subnet, otherIpv4SubnetMaskGuess);

                        OutConsole("  -> Checking {0}:{1}\n", 4, new object[] { otherSccmSitesIp4SubnetBound.SiteCode, ConvertIpInt322Octet(otherIpv4Subnet) });
                        OutConsole(" -> Subnet mask {0} guessed as SCCM does not store it", 4, new object[] { ConvertIpInt322Octet(otherIpv4SubnetMaskGuess) }, ConsoleColor.Yellow);
                        
                        if (IsOverlap(thisIpv4Subnet, thisIpv4SubnetHighIp, otherIpv4Subnet, otherIpv4SubnetHighIp))
                        {
                            builder = new StringBuilder();
                            builder.Append("       POSSIBLE CONFLICT\n         Site1: ");
                            builder.Append(thisSccmSiteIp4SubnetBound.SiteCode);
                            builder.Append(", Boundary ");
                            builder.Append(ConvertIpInt322Octet(thisIpv4Subnet));
                            builder.Append("/");
                            builder.Append(ConvertIpInt322Octet(thisIpv4SubnetMaskGuess));
                            builder.Append("\n         Site2: ");
                            builder.Append(otherSccmSitesIp4SubnetBound.SiteCode);
                            builder.Append(", Boundary ");
                            builder.Append(ConvertIpInt322Octet(otherIpv4Subnet));
                            builder.Append("/");
                            builder.Append(ConvertIpInt322Octet(otherIpv4SubnetMaskGuess));
                            builder.Append("\n         (subnet masks are guessed due to SCCM not storing them)\n");
                            OutConsole(builder.ToString(), 0, ConsoleColor.Red);
                            builder.Clear();
                        }
                    }
                }
                sccmSiteRow.IsProcessed = true;
            }

            OutConsole("Boundary Overlap Check... Done!\n\n", 1);

        }


        private static void PopulateSccmSiteList()
        {
            OutConsole("Creating Unique List of SCCM Sites...\n", 1);

            ArrayList distinctSccmSites = new ArrayList();
            foreach (SccmBoundaryHealth.ds.SccmBoundaryDataRow row in ds.SccmBoundaryData.Rows)
            {
                if (!distinctSccmSites.Contains(row.SiteCode.ToUpper()))
                {
                    distinctSccmSites.Add(row.SiteCode.ToUpper());
                }
            }

            foreach (string distinctSccmSite in distinctSccmSites)
            {
                OutConsole(" -> {0}\n", 2, new object[] { distinctSccmSite } );
                ds.SccmSites.AddSccmSitesRow(distinctSccmSite, false);
            }
            
            OutConsole("Creating Unique List of SCCM Sites... Done!\n\n", 1);
        }


        private static void ReportAdSiteBoundaryHealth()
        {
            OutConsole("Check for unassigned AD sites...\n", 1);

            // get AD sites in use by SCCM
            ds.SccmBoundaryDataRow[] sccmAdSiteBounds = (ds.SccmBoundaryDataRow[]) ds.SccmBoundaryData.Select("BoundaryType = '" + SCCM_BOUND_ADSITE + "'");

            // Get all AD sites from AD
            //ArrayList adSites = new ArrayList();
            
            //foreach (SccmBoundaryHealth.ds.ADSiteDataRow adSite in ds.ADSiteData)
            //{
            //    if (!adSites.Contains(adSite.ADSiteName.ToUpper()))
            //    {
            //        adSites.Add(adSite.ADSiteName.ToUpper());
            //    }
            //}

            //foreach (string adSite in adSites)
            foreach (ds.ADSiteDataRow adSite in ds.ADSiteData)
            {
                OutConsole(" -> Checking AD Site {0}\n", 3, new object[] { adSite.ADSiteName });
                
                bool foundFlag = false;

                foreach (SccmBoundaryHealth.ds.SccmBoundaryDataRow sccmAdSiteBound in sccmAdSiteBounds)
                {
                    if (adSite.ADSiteName.ToUpper() == sccmAdSiteBound.BoundaryData.ToUpper())
                    {
                        foundFlag = true;
                        break;
                    }
                }

                if (!foundFlag)
                {
                    OutConsole(" -> Unassigned AD site boundary: {0}\n", 0, new object[] { adSite.ADSiteName }, ConsoleColor.Yellow);
                }
            }

            OutConsole("Check for unassigned AD sites... Done!\n\n", 0);

            OutConsole("Check for non-existant AD site boundaries...\n", 0);

            foreach (ds.SccmBoundaryDataRow sccmAdSiteBound in sccmAdSiteBounds)
            {

                OutConsole(" -> Checking SCCM AD boundary {0}:{1}\n", 3, new object[] { sccmAdSiteBound.SiteCode, sccmAdSiteBound.BoundaryData });

                // query the AD site data for the AD site name used in SCCM
                ds.ADSiteDataRow[] doesSiteExist = (ds.ADSiteDataRow[])ds.ADSiteData.Select("ADSiteName = '" + sccmAdSiteBound.BoundaryData.ToString() + "'");

                if (doesSiteExist.Count() == 0)
                {
                    OutConsole(" -> SCCM AD site boundary no longer exists: {0}:{1}\n", 0, new object[] { sccmAdSiteBound.SiteCode, sccmAdSiteBound.BoundaryData }, ConsoleColor.Yellow);
                }
            }

            OutConsole("Check for non-existent AD site boundaries... Done!\n\n", 1);
        }


        private static string Returnpassword()
        {
            string pw = "";
            ConsoleKeyInfo info = Console.ReadKey(true);
            while (info.Key != ConsoleKey.Enter)
            {
                if (info.Key != ConsoleKey.Backspace)
                {
                    pw = pw + info.KeyChar;
                    info = Console.ReadKey(true);
                }
                else if (info.Key == ConsoleKey.Backspace)
                {
                    if (!string.IsNullOrEmpty(pw))
                    {
                        pw = pw.Substring(0, pw.Length - 1);
                    }
                    info = Console.ReadKey(true);
                }
            }
            return pw;
        }
    }
}

